import java.io.File;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import weka.classifiers.CostMatrix;
import weka.classifiers.Evaluation;
import weka.classifiers.meta.CostSensitiveClassifier;
import weka.classifiers.trees.J48;
import weka.core.Instances;
import weka.core.TechnicalInformation;

/**
 * Each thread needs to be ran independently from the other.
 * but with this layout you can start one per processor core
 * to optimize CPU usage...
 * @author nason
 * Test
 *
 */
public class J48Thread extends Thread{
	Instances data;
	float varConfidenceFactorStart = 0.25f;
	float varConfidenceFactorStep  = 0.01f;
	float varConfidenceFactorEnd   = varConfidenceFactorStart + varConfidenceFactorStep;
	int varMinNumObjStart = 2;
	int varMinNumObjStep  = 1;
	int varMinNumObjEnd   = varMinNumObjStart + varMinNumObjStep;
	int varNumFoldsStart = 3;
	int varNumFoldsStep  = 1;
	int varNumFoldsEnd   = varNumFoldsStart + varNumFoldsStep;
	int varSeedStart = 1;
	int varSeedStep  = 1;
	int varSeedEnd   = varSeedStart + varSeedStep;
	int varReducedErrorPruningStart = 0;
	int varReducedErrorPruningStop  = 0;
	int varSaveInstanceDataStart = 0;
	int varSaveInstanceDataStop  = 0;
	int varSubtreeRaisingStart = 1;
	int varSubtreeRaisingStop  = 1;
	int varUnprunedStart = 0;
	int varUnprunedStop  = 0;
	int varUseLaplaceStart = 0;
	int varUseLaplaceStop  = 0;
	int varBinarySplitStart = 0;
	int varBinarySplitStop  = 0;
	Timestamp timeStart = new Timestamp(System.currentTimeMillis());
	Timestamp timeStop  = new Timestamp(System.currentTimeMillis());
	int numberOfLoops = 0;
	boolean finished = false;
	List<J48> bestModels;
	List<Evaluation> bestModelsEval;
	boolean costMatrix;
	
	J48Thread(Instances data, int saveTop, boolean costMatrix){
		this.costMatrix = costMatrix;
		this.data = data;
		bestModels = new ArrayList<J48>(saveTop);
		bestModelsEval = new ArrayList<Evaluation>(saveTop);
		bestModelsEval = Collections.synchronizedList(bestModelsEval);
		bestModels = Collections.synchronizedList(bestModels);
		for(int k = 0; k < saveTop ; k++){
			bestModelsEval.add(k, null);
			bestModels.add(k, null);
		}
	}
	
	public void setConfidenceFactor(float start, float end, float step){
		varConfidenceFactorStart = start;
		varConfidenceFactorEnd = end+step;
		varConfidenceFactorStep = step;
	}
	
	public void setNumFolds(int start, int end, int step){
		varNumFoldsStart = start;
		varNumFoldsEnd = end+step;
		varNumFoldsStep = step;
	}
	
	public void setMinNumObjects(int start, int end, int step){
		varMinNumObjStart = start;
		varMinNumObjEnd = end+step;
		varMinNumObjStep = step;
	}
	
	public void setSeed(int start, int end, int step){
		varSeedStart = start;
		varSeedEnd = end+step;
		varSeedStep = step;
	}
	
	public void setReducedErrorPruning(String trueFalseBoth){
		varReducedErrorPruningStart = "false".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 0 : 1;
		varReducedErrorPruningStop = "true".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 1 : 0;
	}
	
	public void setSaveInstanceData(String trueFalseBoth){
		varSaveInstanceDataStart = "false".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 0 : 1;
		varSaveInstanceDataStop = "true".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 1 : 0;
	}
	
	public void setSubtreeRaising(String trueFalseBoth){
		varSubtreeRaisingStart = "false".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 0 : 1;
		varSubtreeRaisingStop = "true".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 1 : 0;
	}
	
	public void setUnpruned(String trueFalseBoth){
		varUnprunedStart = "false".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 0 : 1;
		varUnprunedStop = "true".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 1 : 0;
	}
	
	public void setUseLaplace(String trueFalseBoth){
		varUseLaplaceStart = "false".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 0 : 1;
		varUseLaplaceStop = "true".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 1 : 0;
	}
	
	public void setBinarySplit(String trueFalseBoth){
		varBinarySplitStart = "false".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 0 : 1;
		varBinarySplitStop = "true".equals(trueFalseBoth) || "both".equals(trueFalseBoth) ? 1 : 0;
	}
	
	public synchronized List<J48> getBestModels() {
		return bestModels;
	}

	public synchronized List<Evaluation> getBestModelsEval() {
		return bestModelsEval;
	}

	public Timestamp getRunTime(){
		return new Timestamp(timeStop.getTime() - timeStart.getTime() + Timestamp.valueOf("0001-01-01 00:00:00.00").getTime());
	}
	
	public synchronized boolean isFinished(){
		return finished;
	}
	
	public synchronized void setFinished(boolean finished){
		this.finished = finished;
	}
	
	public void run(){
		try{
			while(!isInterrupted())
			{
				if(!isFinished())
				{
					int k = 0 ;
					numberOfLoops = Math.round((((varConfidenceFactorEnd - varConfidenceFactorStart)/varConfidenceFactorStep))) * Math.round(((float)(varNumFoldsEnd - varNumFoldsStart)/varNumFoldsStep)) * Math.round(((float)(varMinNumObjEnd - varMinNumObjStart)/varMinNumObjStep)) * Math.round(((float)(varSeedEnd - varSeedStart)/varSeedStep)) * (varReducedErrorPruningStart-varReducedErrorPruningStop==0 ? 1 : 2) * (varSaveInstanceDataStart-varSaveInstanceDataStop==0 ? 1 : 2) * (varSubtreeRaisingStart-varSubtreeRaisingStop==0 ? 1 : 2) * (varUnprunedStart-varUnprunedStop==0 ? 1 : 2) * (varUseLaplaceStart-varUseLaplaceStop==0 ? 1 : 2) * (varBinarySplitStart-varBinarySplitStop==0 ? 1 : 2);
					timeStart = new Timestamp(System.currentTimeMillis());
					for(float varConfidenceFactor = varConfidenceFactorStart; varConfidenceFactor < varConfidenceFactorEnd ; varConfidenceFactor += varConfidenceFactorStep){
						for(int varMinNumObj = varMinNumObjStart; varMinNumObj < varMinNumObjEnd ; varMinNumObj += varMinNumObjStep){
							for(int varNumFolds = varNumFoldsStart; varNumFolds < varNumFoldsEnd ; varNumFolds += varNumFoldsStep){
								for(int varSeed = varSeedStart; varSeed < varSeedEnd ; varSeed += varSeedStep){
									for(int varReducedErrorPruning = varReducedErrorPruningStart; varReducedErrorPruning <= varReducedErrorPruningStop ; varReducedErrorPruning++){
										for(int varSaveInstanceData = varSaveInstanceDataStart; varSaveInstanceData <= varSaveInstanceDataStop ; varSaveInstanceData++){
											for(int varSubtreeRaising = varSubtreeRaisingStart; varSubtreeRaising <= varSubtreeRaisingStop ; varSubtreeRaising++){
												for(int varUnpruned = varUnprunedStart; varUnpruned <= varUnprunedStop ; varUnpruned++){
													for(int varUseLaplace = varUseLaplaceStart; varUseLaplace <= varUseLaplaceStop ; varUseLaplace++){
														for(int varBinarySplit = varBinarySplitStart; varBinarySplit <= varBinarySplitStop ; varBinarySplit++){
															
															// classifier
															J48 j48 = new J48();
															
															j48.setBinarySplits(varBinarySplit==1);
															j48.setConfidenceFactor(varConfidenceFactor);
															j48.setMinNumObj(varMinNumObj);
															j48.setNumFolds(varNumFolds);
															j48.setReducedErrorPruning(varReducedErrorPruning==1);
															j48.setSaveInstanceData(varSaveInstanceData==1);
															j48.setSeed(varSeed);
															j48.setSubtreeRaising(varSubtreeRaising==1);
															j48.setUnpruned(varUnpruned==1);
															j48.setUseLaplace(varUseLaplace==1);

															Evaluation eval = new Evaluation(data);
															if(costMatrix){
																CostMatrix cm = new CostMatrix(3);
																cm.setElement(0, 0, 0.0);
																cm.setElement(1, 0, 3.32);
																cm.setElement(2, 0, 8.44);
	
																cm.setElement(0, 1, 1.0);
																cm.setElement(1, 1, 0.0);
																cm.setElement(2, 1, 8.44);
	
																cm.setElement(0, 2, 1.0);
																cm.setElement(1, 2, 3.32);
																cm.setElement(2, 2, 0.0);
																
																CostSensitiveClassifier ccc = new CostSensitiveClassifier();
																ccc.setClassifier(j48);
																ccc.setCostMatrix(cm);
																ccc.setMinimizeExpectedCost(false);
	//															ccc.setOnDemandDirectory(new File("."));
																ccc.setSeed(1);
																
																eval.crossValidateModel(ccc, data, 10, new Random(1));
															}else{
																eval.crossValidateModel(j48, data, 10, new Random(1));
															}
															
															if(bestModelsEval.get(bestModelsEval.size()-1) == null || eval.pctCorrect() > bestModelsEval.get(bestModelsEval.size()-1).pctCorrect()){
																for(int p = 0; p < bestModelsEval.size(); p++){
																	if(bestModelsEval.get(p) == null || eval.pctCorrect() > bestModelsEval.get(p).pctCorrect()){
																		bestModelsEval.remove(bestModelsEval.size()-1);
																		bestModels.remove(bestModels.size()-1);
																		bestModelsEval.add(p, eval);
																		bestModels.add(p, j48);
																		break;
																	}
																}
															}
															
															k++;
															if(k % 1000 == 20)
															{
																long i = System.currentTimeMillis() - timeStart.getTime();
																long j = i/k;
																long l = j * numberOfLoops;
																timeStop = new Timestamp(System.currentTimeMillis() + l);
																System.out.println(k + "/" + numberOfLoops + " " + eval.pctCorrect() + " estimated finish time " + timeStop);
															}
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
					for(int p = bestModelsEval.size()-1; p >= 0; p--)
					{
						bestModelsEval.remove(null);
						bestModels.remove(null);
					}
					timeStop = new Timestamp(System.currentTimeMillis());
					setFinished(true);
				}
				
				sleep(1000000);
			}
		} catch(InterruptedException e){
			// TODO Auto-generated catch block
//			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
